package main

import (
	"fmt"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"github.com/satori/go.uuid"
	"path"
	"net/http"
	"strconv"
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"strings"
	"mime/multipart"
	// "os"
	"github.com/gorilla/websocket"
	"encoding/json"

)

var db *gorm.DB

var fileListMap map[string][]Chunk // key就是文件产生的uuid 返回给前端，然后前端请求merge，然后根据这个uuid去找到对应的fileList 进行合并

var fileList []Chunk // 保存文件块

var upGrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

type BigFileInfo struct {
	OssFileName string 
	TrueName string 
	Url string 
	FileClass string 
	Identifier string // 文件的上传标示，合并的时候需要用
}

type Sizer interface {
	Size() int64
}

type Chunk struct {
	OssFileName string 
	File multipart.File
	Number int 
	Size int64
}

type Image struct {
	Id   int    `gorm:"size:11;primary_key;AUTO_INCREMENT;not null" json:"id"`
	Url  string    `gorm:"size:255;DEFAULT NULL" json:"url"`
	FileName string `gorm:"size:255;DEFAULT NULL" json:"file_name"`
	TrueName string `gorm:"size:255" json:"true_name"`
	FileClass string `gorm:"size:255" json:"file_class"`
	//gorm后添加约束，json后为对应mysql里的字段
}

func (Image) TableName() string {
	return "image_bed"
  }

func handleFile(c *gin.Context) {
	// 获取uuid
	u1 := uuid.Must(uuid.NewV4())
	file, header, err := c.Request.FormFile("file")
	if err != nil {
		c.String(http.StatusBadRequest, "Bad request")
		return
	}
	// header调用Filename方法，就可以得到文件名
	filename := header.Filename
	defer file.Close()
	
	extName := path.Ext(filename)//得到文件扩展名
	// fmt.Println(extName)
	ossFileName := u1.String() + extName
	fileClass := "0"
	if extName != ".png" && extName != ".jpg" && extName != ".jpeg" {
		// 表示上传的是文件而不是图片
		fileClass = "1"
	}
	trueName := strings.TrimSuffix(filename, extName)

	client, err := oss.New("oss-cn-beijing.aliyuncs.com", "LTAIOpVRlAO3McT2", "j6GRyKp8GPX0xtLhhAPI4iaGfxWEys")
    if err != nil {
        fmt.Println("creat client failed")
    }
    bucket, err := client.Bucket("blog-image-xzh")
    if err != nil {
		// HandleError(err)
		fmt.Println("find bucket failed")
    }
	err = bucket.PutObject(ossFileName, file)
    if err != nil {
		// HandleError(err)
		fmt.Println("upload file err is ", err.Error())
	}
	//到这证明上传成功了～
	prefixURL := "https://blog-image-xzh.oss-cn-beijing.aliyuncs.com/"
	url := prefixURL + ossFileName
	//将url跟图片名字存到数据库里面去
	image := Image {
		Url: url,
		FileName: ossFileName,
		TrueName: trueName,
		FileClass: fileClass,
	}
	fmt.Println(image)
	db.Create(&image)
	c.JSON(http.StatusOK, &image)
}

func searchImage(c *gin.Context) {
	// start: 
	// limit:
	// return : image list 
	var images []Image
	start, _ := strconv.Atoi(c.PostForm("start"))
	limit, _ := strconv.Atoi(c.PostForm("limit"))
	fileClass := c.PostForm("file_class")
	if limit == 0 {
		limit = 1
	}
	db.Order("id desc").Where("file_class = ?", fileClass).Offset(start).Limit(limit).Find(&images)
	c.JSON(http.StatusOK, &images)
}

func handleBigFile(c *gin.Context) {

	file, header, err := c.Request.FormFile("file")
	if err != nil {
		c.String(http.StatusBadRequest, "Bad request")
		return
	}
	identifier := c.PostForm("identifier")
	fmt.Println(identifier)
	chunkNumber := c.PostForm("chunkNumber")
	// fmt.Println(chunkNumber)
	// totalChunkNum := c.PostForm("totalChunks")

	chunkSize := c.PostForm("currentChunkSize")
	// fmt.Println(chunkSize)
	// fmt.Println(totalChunkNum + "total num ")
	filename := header.Filename
	chunkNumberInt, _ := strconv.Atoi(chunkNumber)
	chunkSizeInt, _ := strconv.ParseInt(chunkSize, 10, 64) // int64
	// totalChunkNumInt, _ := strconv.Atoi(totalChunkNum)
	// fmt.Println(len(fileList))
	ossFileName := ""
	if chunkNumber == "1"  {
		//第一块
		// 获取uuid
		u1 := uuid.Must(uuid.NewV4())
		file, header, err := c.Request.FormFile("file")
		if err != nil {
			c.String(http.StatusBadRequest, "Bad request")
			return
		}
		// header调用Filename方法，就可以得到文件名
		filename := header.Filename
		defer file.Close()
		
		extName := path.Ext(filename)//得到文件扩展名
		// fmt.Println(extName)
		ossFileName = u1.String() + extName // 返回给前端

		fileList = fileList[0:0] // 清空数组
		
		fileList = append(fileList, Chunk{
			File: file, 
			Number: chunkNumberInt,
			Size: chunkSizeInt,
			OssFileName: ossFileName,
		})
		fmt.Println(fileList)
		fileListMap[identifier] = fileList

	} else {
		fileListMap[identifier] = append(fileListMap[identifier], Chunk{
			File: file, 
			Number: chunkNumberInt,
			Size: chunkSizeInt,
			OssFileName: ossFileName,
		})
		// fileList = append(fileList, Chunk{
		// 	File: file, 
		// 	Number: chunkNumberInt,
		// 	Size: chunkSizeInt,
		// })

	}

	// if len(fileList) == totalChunkNumInt {
	// 	// 最后一块
	// 	// fmt.Println("helloworld")
	// 	mergeFile(fileList, filename)
	// }

	defer file.Close()

	//返回给前端这些大文件的信息，然后前端请求merge接口，进行合并，插入数据库
	c.JSON(http.StatusOK, &BigFileInfo{
		TrueName: filename,
		FileClass: "2",
		Identifier: identifier,
	})

}

func mergeFile(c *gin.Context) {
	//升级get请求为webSocket协议
	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		return
	}
	defer ws.Close()
	for {
		// 读取ws中的数据
		_, message, err := ws.ReadMessage()
		if err != nil {
			// 客户端关闭连接时也会进入
			fmt.Println(err)
			break
		}
		// msg := &data{}
		// json.Unmarshal(message, msg)
		// fmt.Println(msg)
		// fmt.Println(mt)
		// fmt.Println(message)
		fileInfo := BigFileInfo{}
		err = json.Unmarshal(message, &fileInfo)
		if err != nil {
			fmt.Println("json 解析错误")
		}
		fmt.Println(fileInfo)
		identifier := fileInfo.Identifier
		trueName := fileInfo.TrueName
		fileClass := fileInfo.FileClass
		//开始上传阿里云了 上传分块.
		client, err := oss.New("oss-cn-beijing.aliyuncs.com", "LTAIOpVRlAO3McT2", "j6GRyKp8GPX0xtLhhAPI4iaGfxWEys")
		if err != nil {
			fmt.Println("creat client failed")
		}
		bucket, err := client.Bucket("blog-image-xzh")
		if err != nil {
			// HandleError(err)
			fmt.Println("find bucket failed")
		}

		// 指定存储类型为标准存储。
		storageType := oss.ObjectStorageClass(oss.StorageStandard)

		ossFileName := fileListMap[identifier][0].OssFileName //从第一块获取到ossFileName
		// 步骤1：初始化一个分片上传事件，并指定存储类型为标准存储。
		imur, err := bucket.InitiateMultipartUpload("bigfile/" + ossFileName, storageType)
		// 步骤2：上传分片。
		var parts []oss.UploadPart
		for i, chunk := range fileListMap[identifier] {
			
			part, err := bucket.UploadPart(imur, chunk.File, chunk.Size, chunk.Number)
			if err != nil {
				fmt.Println("Error:", err)
				return 
			}
			parts = append(parts, part)
			fmt.Println("块 " + strconv.Itoa(chunk.Number) + "上传成功")
			var percent int = i * 100 / len(fileListMap[identifier])
			// fmt.Println(percent)
			// fmt.Println(len(fileListMap[identifier]))
			// fmt.Println(chunk.Number)
			v := gin.H{"percent": strconv.Itoa(percent), "res": "loading"}
			err = ws.WriteJSON(v)
			if err != nil {
				break
			}
		}

		// 指定Object的读写权限为公共读，默认为继承Bucket的读写权限。
		objectAcl := oss.ObjectACL(oss.ACLPublicRead)

		// 步骤3：完成分片上传，指定文件读写权限为公共读。
		cmur, err := bucket.CompleteMultipartUpload(imur, parts, objectAcl)
		if err != nil {
			fmt.Println("Error:", err)
			return 
		}
		fmt.Println("cmur:", cmur)
		prefixURL := "https://blog-image-xzh.oss-cn-beijing.aliyuncs.com/bigfile/"
		url := prefixURL + ossFileName
		// 没问题了，写入数据库
		uploadFile := Image {
		Url: url,
		FileName: ossFileName,
		TrueName: trueName,
		FileClass: fileClass,
		}
		// fmt.Println(image)
		db.Create(&uploadFile)
		v := gin.H{"res": "loaded", "percent": "100"}
		err = ws.WriteJSON(v)
		if err != nil {
			break
		}
		// c.JSON(http.StatusOK, "success")

		// // 如果客户端发送ping就返回pong,否则数据原封不动返还给客户端
		// if string(message) == "ping" {
		// 	message = []byte("pong")
		// }
		// 写入ws数据 二进制返回
		// err = ws.WriteMessage(mt, message)
		// 返回JSON字符串，借助gin的gin.H实现
		// v := gin.H{"message": msg}
		// err = ws.WriteJSON(v)
		// if err != nil {
		// 	break
		// }
	}

	// identifier := c.PostForm("Identifier")
	// trueName := c.PostForm("TrueName")
	// fileClass := c.PostForm("FileClass")

	// fmt.Println("分块接收完毕，开始上传~")
	// client, err := oss.New("oss-cn-beijing.aliyuncs.com", "LTAIOpVRlAO3McT2", "j6GRyKp8GPX0xtLhhAPI4iaGfxWEys")
    // if err != nil {
    //     fmt.Println("creat client failed")
    // }
    // bucket, err := client.Bucket("blog-image-xzh")
    // if err != nil {
	// 	// HandleError(err)
	// 	fmt.Println("find bucket failed")
	// }

	// // 指定存储类型为标准存储。
	// storageType := oss.ObjectStorageClass(oss.StorageStandard)

	// ossFileName := fileListMap[identifier][0].OssFileName //从第一块获取到ossFileName
	// // 步骤1：初始化一个分片上传事件，并指定存储类型为标准存储。
	// imur, err := bucket.InitiateMultipartUpload("bigfile/" + ossFileName, storageType)
	// // 步骤2：上传分片。
	// var parts []oss.UploadPart
	// for _, chunk := range fileListMap[identifier] {
		
	// 	part, err := bucket.UploadPart(imur, chunk.File, chunk.Size, chunk.Number)
	// 	if err != nil {
	// 		fmt.Println("Error:", err)
	// 		os.Exit(-1)
	// 	}
	// 	parts = append(parts, part)
	// 	fmt.Println("块 " + strconv.Itoa(chunk.Number) + "上传成功")
	// }

	// // 指定Object的读写权限为公共读，默认为继承Bucket的读写权限。
	// objectAcl := oss.ObjectACL(oss.ACLPublicRead)

	// // 步骤3：完成分片上传，指定文件读写权限为公共读。
	// cmur, err := bucket.CompleteMultipartUpload(imur, parts, objectAcl)
	// if err != nil {
	// 	fmt.Println("Error:", err)
	// 	os.Exit(-1)
	// }
	// fmt.Println("cmur:", cmur)
	// prefixURL := "https://blog-image-xzh.oss-cn-beijing.aliyuncs.com/bigfile/"
	// url := prefixURL + ossFileName
	//  // 没问题了，写入数据库
	// uploadFile := Image {
	// Url: url,
	// FileName: ossFileName,
	// TrueName: trueName,
	// FileClass: fileClass,
	// }
	// // fmt.Println(image)
	// db.Create(&uploadFile)
	// c.JSON(http.StatusOK, "success")
 }


func main() {
	fileListMap = make(map[string][]Chunk)
	var err error
	db, err = gorm.Open("mysql", "root:f408909685@tcp(115.28.79.206:3306)/book_xzh?charset=utf8")
	if err != nil {
		fmt.Println("failed to connect database:", err)
		return
	}
	defer db.Close()
	Router()
}
func Router() {
	router := gin.Default()
	//路径映射
	router.POST("/api/upload_image", handleFile)
	router.POST("/api/search_image", searchImage)
	router.POST("/api/upload_bigfile", handleBigFile)
	router.GET("/api/merge_file", mergeFile)
	router.Run(":9090")
}